package pl.darbat.photogallery.list;

import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.RejectedExecutionException;

import pl.darbat.photogallery.Debug;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

public class ListDownloader<DownloadParameter, ResultData> {

    private static final String TAG = "ListDownloader";
    private static final int MAX_BYTES_CAPACITY = 1500000;
    private int HARD_CACHE_CAPACITY = 50;
    private int DYNAMIC_CACHE_CAPACITY = HARD_CACHE_CAPACITY;
    private DownloadAction<DownloadParameter, ResultData> mDownloaderInterface;

    public ListDownloader(DownloadAction<DownloadParameter, ResultData> di) {
        mDownloaderInterface = di;
    }
    
    public ListDownloader(DownloadAction<DownloadParameter, ResultData> di, int cacheSize) {
        mDownloaderInterface = di;
        HARD_CACHE_CAPACITY = cacheSize;
        DYNAMIC_CACHE_CAPACITY = cacheSize;
    }

    public void downloadWithCache(DownloadParameter id, View view) {

        ResultData result = mResultCache.get(id);
        // NULL indicates that number haven't got assigned name
        if (result == null) {
            if (mResultCache.containsKey(id)) {
                return; // This is a null entry 
            } else {
            	mDownloaderInterface.setLoading(view);
                download(id, view);
            }
        } else {
            // Log.v(TAG, "Result from cache");
            mDownloaderInterface.applyDownloadedResult(result, view, true);
        }

    }

    public void downloadWithCache(DownloadParameter id, View view, ResultData placeholder) {

        ResultData result = mResultCache.get(id);
        // NULL indicates that number haven't got assigned name
        if (result == null) {
            if (mResultCache.containsKey(id)) {
                mDownloaderInterface.applyDownloadedResult(placeholder, view, true);
                return; // This is a null entry 
            } else {
            	mDownloaderInterface.setLoading(view);
                download(id, view);
            }
        } else {
            // Log.v(TAG, "Result from cache");
            mDownloaderInterface.applyDownloadedResult(result, view, true);
        }

    }
 
    public void getFromCache(DownloadParameter id, View view, ResultData placeholder) {
        ResultData result = mResultCache.get(id);
        // NULL indicates that number haven't got assigned name
        if (result == null) {
            mDownloaderInterface.applyDownloadedResult(placeholder, view, true);
        } else {
            // Log.v(TAG, "Result from cache");
            mDownloaderInterface.applyDownloadedResult(result, view, true);
        }
    }
    
    @SuppressWarnings("unchecked")
    public void download(DownloadParameter id, View view) {
        // Log.v(TAG, "download");
        if (id == null) {
            mDownloaderInterface.applyDownloadedResult(null, view, true);
            return;
        }

        if (cancelPotentialDownload(id, view)) {
            DownloaderTask task = new DownloaderTask(view);
            view.setTag(task);

            try {
                task.execute(id);
            } catch (RejectedExecutionException e) {
                Log.w(TAG, "Can't execute task!", e);
            }
        }
    }

    private boolean cancelPotentialDownload(DownloadParameter id, View view) {
        DownloaderTask downloaderTask = getDownloaderTask(view);

        if (downloaderTask != null) {
            DownloadParameter downloadId = downloaderTask.mId;
            if ((downloadId == null) || (!downloadId.equals(id))) {
            	mDownloaderInterface.cancelDownload();
                downloaderTask.cancel(true);
            } else {
                // The same URL is already being downloaded.
                return false;
            }
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    private DownloaderTask getDownloaderTask(View view) {
        if (view != null) {
           	Object task = view.getTag();
           	if(task != null && task instanceof ListDownloader.DownloaderTask){
           		return (DownloaderTask) task;
           	}
        }
        return null;
    }

    class DownloaderTask extends AsyncTask<DownloadParameter, Void, ResultData> {

        private DownloadParameter mId;
        private int mSize;
        private final WeakReference<View> mViewReference;

        public DownloaderTask(View view) {
        	if(Debug.LISTDOWNLOADER) Log.v(TAG,"DownloaderTask");
            mViewReference = new WeakReference<View>(view);
            if(view instanceof ImageView){
            	ImageView imageview = (ImageView) view;
            	int width = imageview.getLayoutParams().width;
            	int height = imageview.getLayoutParams().height;
            	mSize = width > height ? width : height; 

            	int cacheSize = MAX_BYTES_CAPACITY/(mSize * mSize * 3);
            	DYNAMIC_CACHE_CAPACITY = cacheSize > HARD_CACHE_CAPACITY ? HARD_CACHE_CAPACITY : cacheSize;
            	if(Debug.LISTDOWNLOADER) Log.v(TAG,"size: " + mSize + ", cache size: " + DYNAMIC_CACHE_CAPACITY);
            }
        }

        /**
         * Actual download method.
         */
        @Override
        protected ResultData doInBackground(DownloadParameter... params) {
            mId = params[0];
            // return ContactAccessor.getInstance(mContext).getBitmapFromId(id);
            return mDownloaderInterface.download(mId, mSize);
        }

        /**
         * Once the image is downloaded, associates it to the imageView
         */
        @Override
        protected void onPostExecute(ResultData result) {
            //Log.v(TAG, "onPostExecute");
            if (isCancelled()) {
                result = null;
                // Log.v(TAG, "Download Canceled");
            }
            synchronized (mResultCache) {
            	if(DYNAMIC_CACHE_CAPACITY > 0) {
            		mResultCache.put(mId, result);
            	}
            }
            if (mViewReference != null) {
                View view = mViewReference.get();
                if (view != null) {
                    if (result != null) {
                        DownloaderTask downloaderTask = getDownloaderTask(view);
                        if (this == downloaderTask) {
                            mDownloaderInterface.applyDownloadedResult(result, view, false);
                            // Log.v(TAG, "Data downloaded");
                        } else {
                            // Log.v(TAG, "Not setting result - other task");
                        }
                    }
                    view.setTag(null);
                }else{
                    Log.d(TAG,"View is null");
                }
            }
        }
    }
    
    public void clearCache() {
//        Iterator<Entry<DownloadParameter, ResultData>> itr = mResultCache.entrySet().iterator();
//        while (itr.hasNext()) {
//        	LinkedHashMap.Entry<DownloadParameter, ResultData> e = (LinkedHashMap.Entry<DownloadParameter, ResultData>)itr.next();
//        	if(e.getValue() instanceof Bitmap){
//        		((Bitmap) e.getValue()).recycle();
//        	}
//        }


        for (ResultData result :  mResultCache.values()) {
        	if(result instanceof Bitmap){
        		((Bitmap) result).recycle();
        	}
        }
    	mResultCache.clear();


    }


    private final HashMap<DownloadParameter, ResultData> mResultCache = new LinkedHashMap<DownloadParameter, ResultData>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {

        private static final long serialVersionUID = -3769629989767188511L;

        @Override
        protected boolean removeEldestEntry(LinkedHashMap.Entry<DownloadParameter, ResultData> eldest) {
            if (size() > DYNAMIC_CACHE_CAPACITY) {
            	if(eldest.getValue() instanceof Bitmap){
            		if(Debug.LISTDOWNLOADER) Log.v(TAG, "Recycling bitmap...");
            		Bitmap b = ((Bitmap)eldest.getValue());
            		this.remove(eldest.getKey());
            		b.recycle();
            		return false;
            	}
            	if(Debug.LISTDOWNLOADER) Log.v(TAG,"removed eldest entry from cache");
                return true;
            } else {
                return false;
            }
        }
        
    };
    
//    private ResultData getBitmapFromCache(DownloadParameter id) {
//        SoftReference<ResultData> softReference = mResultCache.get(id);
//        if (softReference != null) {
//            final ResultData result = softReference.get();
//            if (result != null) {
//                // Result found in soft cache
//                return result;
//            } else {
//                // Soft reference has been Garbage Collected
//            	mResultCache.remove(id);
//            }
//        }
//        return null;
//    }
}